/*
 ============================================================================
 Name        : main.c
 Author      : Agustin Pivetta, Diego Rivas
 Version     : 1.0
 Description : Tp1 66.20: Dump Octal en MIPS
 ============================================================================
 */

#include <stdbool.h>
#include <string.h>
#include <getopt.h>
#include "odump.h"

//Extras hasta que se complete el desarrollo en assembly @BORRAR
#include <unistd.h>

#define MIN_NUM_ASCII 48
#define MAX_NUM_ASCII 57

/*
 * Imprimo la ayuda
 */
void printHelp() {
	printf(
			"Usage:\n"
					"  tp1 -h\n"
					"  tp1 -V\n"
					"  tp1 [options]\n"
					"Options:\n"
					"  -h, --help      Print this information.\n"
					"  -V, --version   Print version and quit.\n"
					"  -O, --octal     Octal dump mode: encode the input (binary file) in \n"
					"                  octal format (default).\n"
					"  -b, --binary    Binary mode: decode the input (octal dump file) in\n"
					"                  binary format.\n"
					"  -i, --input     Path to input file (defaults to stdin).\n"
					"  -o, --output    Path to output file (defaults to stdout).\n"
					"  -l, --length    Number of octal bytes per line (line length) at the\n"
					"                  output in octal dump mode (0 = default = no limit).\n"
					"  -d, --delimiter Delimiter between octal bytes. Could be a string, not\n"
					"                  only a single byte. Used as delimiter for octal output\n"
					"                  and input files (default: none).\n");

}

/*
 * Imprimo la version
 */
void printVersion() {
	printf("66.20 - TP1: Octal Dump, Versión 1.00\n");
	return;
}
/*
 * Parseo number y si es un numero entero valido lo casteo a entero
 */
size_t parseInt(const char* number) {
	char len = strlen(number);

	// Si me envían un cero devuelvo 0
	if ((len == 1) && (number[0] == '0'))
		return 0;

	size_t i = 0;
	// Recorro a m y verifico que cada uno de sus caracteres sean números
	for (i = 0; i < len; i++) {
		if ((number[i] < MIN_NUM_ASCII) || (number[i] > MAX_NUM_ASCII))
			return 0;
	}

	// Si m es una cadena válida la convierto en un número
	unsigned int aux = 0;
	sscanf(number, "%u", &aux);
	return aux;
}

/*
 * Abro los archivos de input/outout
 */
FILE* openFile(char* fileName, FILE* default_file, const char* mode) {
	FILE* fd;
	if (fileName == NULL ) {
		fd = default_file;
	} else {
		fd = fopen(fileName, mode);
	}

	if (!fd) {
		fprintf(stderr, odump_errmsg[3], fileName);
		exit(-1);
	}
	return fd;
}

int main(int argc, char** argv) {
	// Fuerzo que un error en getopt no se imprima por stderr
	opterr = 0;
	// Indica cuando terminé de procesar todos los argumentos
	int nextOpt = 0;
	// Cadena que lista las opciones cortas válidas
	const char* const opCortas = "hVObi:o:l:d:";
	// Estructura de structs describiendo los valores largos
	const struct option opLargas[] = { { "help", no_argument, NULL, 'h' }, {
			"version", no_argument, NULL, 'V' }, { "octal", no_argument, NULL,
			'O' }, { "binary", no_argument, NULL, 'b' }, { "input",
			optional_argument, NULL, 'i' }, { "output", optional_argument, NULL,
			'o' }, { "length", optional_argument, NULL, 'l' }, { "delimiter",
			optional_argument, NULL, 'd' }, { NULL, no_argument, NULL, 0 } };

	// Variable donde recibo el nombre del archivo de entrada
	char* inputFName = NULL;
	// Variable donde recibo el nombre del archivo de salida
	char* outputFName = NULL;
	// Variable donde guardo el modo de ejecucion (1:octal dump mode | 2: binary dump mode)
	char mode = 1;
	// Variable donde guardo la longitud de numeros octal por linea
	size_t length = 0;
	// Variable donde guardo el delimitador de bytes octales
	char* delimiter = NULL;
	//Retorno de las funciones de encode y decode
	int rc = 0;

	while (1) {
		//Leo que me devuelve get_opt
		nextOpt = getopt_long(argc, argv, opCortas, opLargas, NULL );

		if (nextOpt == -1) {
			break;
		}

		switch (nextOpt) {
		case 'h': {
			printHelp();
			return 0;
			break;
		}

		case 'V': {
			printVersion();
			return 0;
			break;
		}

		case 'O': {
			mode = 1;
			break;
		}

		case 'b': {
			mode = 2;
			break;
		}

		case 'i': {
			if (strcmp(optarg, "-") != 0) {
				inputFName = malloc(strlen(optarg) + 1);
				strcpy(inputFName, optarg);
			}
			break;
		}

		case 'o': {
			if (strcmp(optarg, "-") != 0) {
				outputFName = malloc(strlen(optarg) + 1);
				strcpy(outputFName, optarg);
			}
			break;
		}

		case 'l': {
			length = parseInt(optarg);
			break;
		}

		case 'd': {
			delimiter = malloc(strlen(optarg) + 1);
			strcpy(delimiter, optarg);
			break;
		}

		default: {
			printHelp();
			exit(-1);
			break;
		}
		}
	}

	FILE* input = openFile(inputFName, stdin, "rb");
	FILE* output = openFile(outputFName, stdout, "w");

	//Obtengo los file descriptors
	int infd = fileno(input);
	int outfd = fileno(output);

	//Seteo las opciones
	struct odump_opts opts = { length, delimiter };

	if (mode == 1) {
		rc = odump_encode(infd, outfd, &opts);
	} else if (mode == 2) {
		rc = odump_decode(infd, outfd, &opts);
	}

	fclose(input);
	fclose(output);

	if (rc != 0) {
		fprintf(stderr, "%s", odump_errmsg[rc]);
        return rc;
	}

	return 0;
}
